
#ifndef MAGNUM_H_
#define MAGNUM_H_

#include <mpfr.h>

#include <list>

#define MAX(a, b) (a > b ? a : b)
#define MIN(a, b) (a < b ? a : b)

class Over100Listener;
class CriticalPrecisionListener;

class MagNum {

	static mpfr_rnd_t RND_MODE;
	static const int INPUT_BASE;
	static int criticalPrecision;

	static bool initialized;

	static const int ADD_CORRECTION;
	static const int SUBTRACT_CORRECTION;
	static const int MULTIPLY_CORRECTION;
	static const int DIVIDE_CORRECTION;
	static const int ROOT_CORRECTION;
	static const int EXP_CORRECTION;
	static const int POW_CORRECTION;
	static const int SIN_CORRECTION;

	mpfr_t number;
	int currentPrecision;
	char** outStringPointer;

	std::list<Over100Listener*> over100Listeners;
	std::list<CriticalPrecisionListener*> criticalPrecisionListeners;

public:
	static int INITIAL_PRECISION;

	static std::list<CriticalPrecisionListener*> globalCriticalPrecisionListeners;

	// constants
	static const MagNum ZERO;
	static const MagNum ONE;
	static const MagNum TWO;

	static void setCriticalPrecision(int precision);
	static int getCriticalPrecision();

	// constructors
	MagNum();
	MagNum(const int given);
	MagNum(const long given);
	MagNum(const double given);
	MagNum(const char* given);
	MagNum(const MagNum& given);

	// TODO: test
	MagNum(mpfr_t* mpfr);

	// destructor
	virtual ~MagNum();

	// getters of value in C types
	int getInt() const;
	double getDouble() const;
	char* getChars() const;

	// overloaded operators
	MagNum& operator=(const MagNum &other);
	MagNum& operator+=(const MagNum &other);
	MagNum& operator-=(const MagNum &other);
	MagNum& operator*=(const MagNum &other);
	MagNum& operator/=(const MagNum &other);


	const MagNum operator+(const MagNum &other) const;
	const MagNum operator-(const MagNum &other) const;
	const MagNum operator*(const MagNum &other) const;
	const MagNum operator/(const MagNum &other) const;

	bool operator==(const MagNum &other) const;
	bool operator!=(const MagNum &other) const;
	bool operator>=(const MagNum &other) const;
	bool operator>(const MagNum &other) const;
	bool operator<=(const MagNum &other) const;
	bool operator<(const MagNum &other) const;


	MagNum operator-();
	MagNum& changeSign();
	MagNum getAbs();
	MagNum& abs();

	MagNum getSqrt() const;
	MagNum& sqrt();
	MagNum getCbrt() const;
	MagNum& cbrt();
	MagNum getRoot(long arg) const;
	MagNum& root(long arg);
	MagNum getPow(const MagNum& arg) const;
	MagNum& pow(const MagNum& arg);

	static MagNum sin(const MagNum& arg);
	static MagNum cos(const MagNum& arg);
	static MagNum tan(const MagNum& arg);
	static MagNum cot(const MagNum& arg);

	static MagNum exp(const MagNum& arg);
	// TODO: test
	static MagNum getPi();

	void print();
	// TODO: test
	friend std::ostream& operator<< (std::ostream &out, const MagNum &magnum);

	int getCurrentPrecision() const;
	void setCurrentPrecision(int precision);

	const mpfr_t* getMpfr() const;
	int getMpfrExp() const;
	static MagNum PI;


	void addOver100Listener(Over100Listener* listener);
	void addCriticalPrecisionListener(CriticalPrecisionListener* listener);
	static void addGlobalCriticalPrecisionListener(CriticalPrecisionListener* listener);

private:

	static bool staticInitialization();

	void init();
	void preinit();

	mp_limb_t* getMpfrD() const;

	MagNum& sinOf();
	MagNum& cosOf();
	MagNum& tanOf();
	MagNum& cotOf();
	MagNum& expOf();

	void evaluateNewPrecision(int oldExp, int oldPrecision, int correction);
	void evaluateNewPrecision(int oldExp1, int oldExp2, int oldPrecision1, int oldPrecision2, int correction);

	void notifyOver100Listeners();
	void notifyCriticalPositionListeners();
};

#endif /* MAGNUM_H_ */
